/**
 * @fileoverview Require usage of specified node modules.
 * @author Rich Trott
 */
'use strict';

const { isRequireCall, isString } = require('./rules-utils.js');

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = function(context) {
  // Trim required module names
  const options = context.options[0];
  const requiredModules = options ? Object.keys(options).map((x) => {
    return [ x, new RegExp(options[x]) ];
  }) : [];
  const isESM = context.parserOptions.sourceType === 'module';

  const foundModules = [];

  // If no modules are required we don't need to check the CallExpressions
  if (requiredModules.length === 0) {
    return {};
  }

  /**
   * Function to check if the path is a required module and return its name.
   * @param {String} str The path to check
   * @returns {undefined|String} required module name or undefined
   */
  function getRequiredModuleName(str) {
    const match = requiredModules.find(([, test]) => {
      return test.test(str);
    });
    return match ? match[0] : undefined;
  }

  /**
   * Function to check if a node has an argument that is a required module and
   * return its name.
   * @param {ASTNode} node The node to check
   * @returns {undefined|String} required module name or undefined
   */
  function getRequiredModuleNameFromCall(node) {
    // Node has arguments and first argument is string
    if (node.arguments.length && isString(node.arguments[0])) {
      return getRequiredModuleName(node.arguments[0].value.trim());
    }

    return undefined;
  }

  const rules = {
    'Program:exit'(node) {
      if (foundModules.length < requiredModules.length) {
        const missingModules = requiredModules.filter(
          ([module]) => foundModules.indexOf(module) === -1
        );
        missingModules.forEach(([moduleName]) => {
          context.report(
            node,
            'Mandatory module "{{moduleName}}" must be loaded.',
            { moduleName: moduleName }
          );
        });
      }
    }
  };

  if (isESM) {
    rules.ImportDeclaration = (node) => {
      const requiredModuleName = getRequiredModuleName(node.source.value);
      if (requiredModuleName) {
        foundModules.push(requiredModuleName);
      }
    };
  } else {
    rules.CallExpression = (node) => {
      if (isRequireCall(node)) {
        const requiredModuleName = getRequiredModuleNameFromCall(node);

        if (requiredModuleName) {
          foundModules.push(requiredModuleName);
        }
      }
    };
  }

  return rules;
};

module.exports.meta = {
  schema: [{
    'type': 'object',
    'additionalProperties': {
      'type': 'string'
    },
  }],
};
